﻿using System;
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
#else
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
#endif

namespace FreshMeat.Xaml
{
   [TemplateVisualState(Name = "Busy", GroupName = "BusyStates")]
   [TemplateVisualState(Name = "NotBusy", GroupName = "BusyStates")]
   [TemplateVisualState(Name = "Passive", GroupName = "BlockingStates")]
   [TemplateVisualState(Name = "Active", GroupName = "BlockingStates")]
   public class BusyIndicator : ContentControl
   {
#if !SILVERLIGHT && !NETFX_CORE
      static BusyIndicator()
      {
         DefaultStyleKeyProperty.OverrideMetadata(typeof(BusyIndicator), new FrameworkPropertyMetadata(typeof(BusyIndicator)));
      }
#endif

      /// <summary>
      /// Indicates whether the busy indicator should be activated.
      /// </summary>
      public readonly static DependencyProperty IsBusyProperty = DependencyProperty.Register(
         "IsBusy",
         typeof(bool),
         typeof(BusyIndicator),
         new PropertyMetadata(false, (d, e) => ((BusyIndicator)d).IsBusyChanged()));

      readonly DispatcherTimer blockingTimer;
      readonly DispatcherTimer cursorTimer;
      FocusContext focusContext;
#if !NETFX_CORE
      Cursor savedCursor;
#endif
      bool isActivelyBlocking;

      /// <summary>
      /// Creates instance.
      /// </summary>
      public BusyIndicator()
      {
         DefaultStyleKey = typeof(BusyIndicator);

         // This timer's interval determines how long we remain in the "Active" blocking state.
         // When the timer expires, the active blocking mode ends, thought we're probably still busy.
         blockingTimer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(0.05)};
         blockingTimer.Tick += (sender, args) =>
                               {
                                  IsActivelyBlocking = false;
                               };

         // This timer's interval determines how long to wait after IsBusy becomes true until the
         // cursor is changed to a wait cursor.
         // BUG: The wait cursor never appears because this timer is never started.
         cursorTimer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(2.0)};
         cursorTimer.Tick += (sender, args) =>
                             {
#if !NETFX_CORE
                                Cursor = Cursors.Wait;
#endif
                             };

         IsTabStop = false;

         KeyDown += KeyPressed;
         KeyUp += KeyReleased;
#if NETFX_CORE
         PointerPressed += MousePressed;
#else
         MouseLeftButtonDown += MousePressed;
         MouseRightButtonDown += MousePressed;
#endif
      }

#if NETFX_CORE
      protected override void OnApplyTemplate()
#else
      public override void OnApplyTemplate()
#endif
      {
         base.OnApplyTemplate();

         UpdateStates(false);
      }

      void UpdateStates(bool useTransitions)
      {
         VisualStateManager.GoToState(this, IsBusy ? "Busy" : "NotBusy", useTransitions);
         VisualStateManager.GoToState(this, IsActivelyBlocking ? "Active" : "Passive", useTransitions);
      }

      public bool IsBusy
      {
         get { return (bool)GetValue(IsBusyProperty); }
         set { SetValue(IsBusyProperty, value); }
      }

      void IsBusyChanged()
      {
         if (IsBusy)
         {
#if BUSYTRACE
            System.Diagnostics.Trace.TraceInformation("BusyIndicator is going to the busy state");
#endif
#if !NETFX_CORE
            // Save the current cursor in case the wait cursor is triggered.
            savedCursor = Cursor;
#endif

            // Set properties so this control is eligible for focus, then give it focus.
            // It is necessary to have focus so that it can intercept keys and prevent
            // them from going to the real controls.
            IsTabStop = true;
#if !SILVERLIGHT && !NETFX_CORE
            Focusable = true;
#endif
            focusContext = FocusHelper.SetTemporaryFocus(this);
         }
         else
         {
#if BUSYTRACE
            System.Diagnostics.Trace.TraceInformation("BusyIndicator is leaving the busy state");
#endif
            // Stop all timers
            blockingTimer.Stop();
            cursorTimer.Stop();

            // Turn off active blocking; not strictly necessary but control template can
            // then rely on it always going to Passive state when it's no longer busy.
            // Don't use property because we'll update states right after this anyway.
            isActivelyBlocking = false; 

            // Restore the focus to wherever it was before we got busy
            focusContext.Restore();

            // Change back to being a non-focusable non-tab-able control
#if !SILVERLIGHT && !NETFX_CORE
            Focusable = false;
#endif
            IsTabStop = false;

#if !NETFX_CORE
            // Restore the cursor in case it was changed
            Cursor = savedCursor;
#endif
         }

         UpdateStates(true);
      }

#if NETFX_CORE
      void MousePressed(object sender, PointerRoutedEventArgs e)
#else
      void MousePressed(object sender, MouseButtonEventArgs e)
#endif
      {
         if (!IsBusy)
            return;

         TriggerActiveBlocking();
      }

#if NETFX_CORE
      void KeyPressed(object sender, KeyRoutedEventArgs e)
#else
      void KeyPressed(object sender, KeyEventArgs e)
#endif
      {
         if (!IsBusy)
            return;

         e.Handled = true;
         TriggerActiveBlocking();
      }

#if NETFX_CORE
      void KeyReleased(object sender, KeyRoutedEventArgs e)
#else
      void KeyReleased(object sender, KeyEventArgs e)
#endif
      {
         if (!IsBusy)
            return;

         e.Handled = true;
         // Don't actively block on key up -- only on key down
      }

      void TriggerActiveBlocking()
      {
         if (!IsBusy)
            return;

         IsActivelyBlocking = true;
      }

      bool IsActivelyBlocking
      {
         get { return isActivelyBlocking; }
         set
         {
            blockingTimer.Stop();
            blockingTimer.Start();

            if (isActivelyBlocking != value)
            {
               isActivelyBlocking = value;

               UpdateStates(true);
            }
         }
      }
   }
}
